#region File Description
//-----------------------------------------------------------------------------
// GameplayScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Threading;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Skyboxes;
#endregion

namespace GameStateManagement
{
    public delegate void GameDelegate();
    class GameplayScreen : GameScreen
    {
        #region Fields

        ContentManager content;
        SpriteFont gameFont;

        Random random = new Random();

        Utils.Timer m_kTimer = new Utils.Timer();

        //DeferredRenderer renderer;

        public static AudioEngine audio;
        public static WaveBank wavebank;
        public static SoundBank soundbank;

        //public static Vector3 AmbientLightColor = new Vector3(0.3f, 0.3f, 0.3f);

        //public static Vector3 SpecularColor = new Vector3(1.0f, 1.0f, 1.0f);
        //public static float SpecularPower = 100.0f;
        //private float m_fCurLightAngle = 3 * MathHelper.PiOver2;
        //public static Vector3 m_vDirLight = new Vector3(-1.0f, 1.0f, 0.0f);
        public static Vector3 DiffuseColor = Color.White.ToVector3();

        public static bool using_mouse = true;

        public static int center_x = 512;
        public static int center_y = 384;
        public static int max_distance_from_center = 300;

        public Vector2 mouse_vector;

        // Follow camera
        public static Camera FollowCamera;

        //Star m_Star;
        public Player m_Player;

        public Planet planet;

        static public bool pause = false;

        public HighScoreManager ScoreManager;
        public CheckpointManager checkpointManager;
        public WormholeManager wormholeManager;
        public PowerupManager powerUpManager;
        public EnemyManager enemyManager;

        public static float currentScore = 0;

        private bool respawning = false;
        public static bool gameOver = false;
        public static float fDelta;

        public SoundEffectInstance soundOverInstance;
        public SoundEffect soundGameOver;

        public Song bgMusic;

        Skybox sky_box;

        public static ShrinkingStarSphere shrinkingStarSphere;

        #endregion

        #region Initialization


        /// <summary>
        /// Constructor.
        /// </summary>
        public GameplayScreen()
        {
            TransitionOnTime = TimeSpan.Zero;
            TransitionOffTime = TimeSpan.Zero;
        }

        /// <summary>
        /// Load graphics content for the game.
        /// </summary>
        public override void LoadContent()
        {
            if (content == null)
                content = new ContentManager(ScreenManager.Game.Services, "Content");

            sky_box = new Skybox("nebula", content);

            gameFont = content.Load<SpriteFont>("gamefont");

            // A real game would probably have more content than this sample, so
            // it would take longer to load. We simulate that by delaying for a
            // while, giving you a chance to admire the beautiful loading screen.
            //Thread.Sleep(1000);

            // once the load has finished, we use ResetElapsedTime to tell the game's
            // timing mechanism that we have just finished a very long frame, and that
            // it should not try to catch up.
            ScreenManager.Game.ResetElapsedTime();

            audio = new AudioEngine("Content/Sounds.xgs");
            wavebank = new WaveBank(audio, "Content/XNAsteroids Waves.xwb");
            soundbank = new SoundBank(audio, "Content/XNAsteroids Cues.xsb");

            // Initialize the shrinking sphere.
            shrinkingStarSphere = ShrinkingStarSphere.InitializeShrinkingStarSphere(ScreenManager.Game, ShrinkingStarSphere.StartSize, 30f, 300f);
            shrinkingStarSphere.StartShrinking(ShrinkingStarSphere.StartSize, ShrinkingStarSphere.LevelPlanetRadius);

            // Initialize manager objects
            checkpointManager = CheckpointManager.InitializeCheckpointManager(ScreenManager.Game);
            wormholeManager = WormholeManager.InitializeWormholeManager(ScreenManager.Game);
            powerUpManager = new PowerupManager(ScreenManager.Game);

            // Create the static wormhole pair.
            shrinkingStarSphere.CreateSphereWormholePair();

            FollowCamera = new Camera(ScreenManager.Game);
            ScreenManager.Game.Components.Add(FollowCamera);

            m_Player = new Player(ScreenManager.Game);
            ScreenManager.Game.Components.Add(m_Player);
            m_Player.m_Ship.m_vWorldPosition = new Vector3(2500f, 0f, 0f);

            float magnitudeRatio = CheckpointManager.MinRadiusMagnitude + ((float)random.NextDouble() * (CheckpointManager.MaxRadiusMagnitude - CheckpointManager.MinRadiusMagnitude));

            // MOVED load_matriced CALL TO Ship.LoadContent().  Just seemed more appropriate.

            powerUpManager.CreateShield(.1f);
            powerUpManager.CreateSpeedBoost(.1f);

            ScoreManager = new HighScoreManager(ScreenManager.Game, new Vector2(800f, 0f));
            ScreenManager.Game.Components.Add(ScoreManager);
            mouse_vector = Vector2.Zero;

            planet = new Planet(ScreenManager.Game);
            ScreenManager.Game.Components.Add(planet);

            soundGameOver = ScreenManager.Game.Content.Load<SoundEffect>("game_over");
            soundOverInstance = soundGameOver.CreateInstance();

            bgMusic = ScreenManager.Game.Content.Load<Song>("Background_Music");
            MediaPlayer.Play(bgMusic);
            MediaPlayer.IsRepeating = true;

            enemyManager = new EnemyManager(ScreenManager.Game, m_Player, planet);
            ScreenManager.Game.Components.Add(enemyManager);

            // Game is no longer over.
            GameplayScreen.gameOver = false;
        }

        /// <summary>
        /// Unload graphics content used by the game.
        /// </summary>
        public override void UnloadContent()
        {
            content.Unload();

            // Delete in reverse order of creation.

            // Delete the player/ship/arrow
            m_Player.Remove();
            ScreenManager.Game.Components.Remove(m_Player);
            m_Player = null;

            // Remove the planet.
            ScreenManager.Game.Components.Remove(planet);
            planet = null;

            // Destroy all the game managers (they will handle the actors they manage)
            WormholeManager.DestroyWormholeManager(ScreenManager.Game);
            CheckpointManager.DestroyCheckpointManager(ScreenManager.Game);
            shrinkingStarSphere.DestroyShrinkingStarSphere(ScreenManager.Game);

            ScreenManager.Game.Components.Remove(ScoreManager);
            ScoreManager.Dispose();
            ScoreManager = null;
            ScreenManager.Game.Components.Remove(powerUpManager);
            powerUpManager.Dispose();
            powerUpManager = null;
        }

        #endregion

        #region Update and Draw


        /// <summary>
        /// Updates the state of the game. This method checks the GameScreen.IsActive
        /// property, so the game will stop updating when the pause menu is active,
        /// or if you tab away to a different application.
        /// </summary>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus,
                                                       bool coveredByOtherScreen)
        {
            base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
            if (!pause && !gameOver)
            {
                m_kTimer.Update(gameTime);

                // PowerUp-Ship collision
                foreach (PowerUp powerup in powerUpManager.powerupList)
                {
                    if (powerup.WorldBounds.Intersects(m_Player.m_Ship.WorldBounds) && powerup.GetDisplayed())
                    {
                        powerup.UseAbility(m_Player);
                        powerup.SetDisplayed(false);
                    }
                }
                powerUpManager.setLocation(shrinkingStarSphere);

                // Ship-Checkpoint collision
                Checkpoint currentCheckpoint = checkpointManager.GetCurrentCheckpoint();
                if (currentCheckpoint != null)
                {
                    if (currentCheckpoint.WorldBounds.Intersects(m_Player.m_Ship.WorldBounds))
                    {
                        currentCheckpoint.ShipCollide();
                    }
                }
                // Wormhole-Object collision
                foreach (Wormhole wormhole in WormholeManager.GetWormholeManager().wormholes)
                {
                    foreach (WormholeObject wormholeTarget in WormholeManager.wormholeObjs)
                    {
                        if (wormhole.WorldBounds.Intersects(wormholeTarget.m_Obj.WorldBounds))
                        {
                            wormhole.ObjCollide(wormholeTarget);
                        }
                    }
                }

                //enemy missile collision
                List<Missile> missile_list = enemyManager.MissileList;
                Stack<Missile> delete_list = new Stack<Missile>();
                foreach (Missile missile in missile_list)
                {
                    if (missile.WorldBounds.Intersects(m_Player.m_Ship.WorldBounds) == true)
                    {
                        m_Player.TakeDamage(5);
                        delete_list.Push(missile);
                    }
                    if (missile.WorldBounds.Intersects(planet.WorldBounds) == true)
                    {
                        delete_list.Push(missile);
                    }
                }
                while (delete_list.Count > 0)
                {
                    enemyManager.remove_missile(delete_list.Pop());
                }

                //player missile collision
                Stack<StaticEnemy> delete_enemy = new Stack<StaticEnemy>();
                delete_list = new Stack<Missile>();
                foreach (Missile missile in m_Player.m_Ship.missiles)
                {
                    if (missile.WorldBounds.Intersects(planet.WorldBounds) == true)
                    {
                        delete_list.Push(missile);
                    }
                    foreach (StaticEnemy enemy in enemyManager.enemy_list)
                    {
                        if (missile.WorldBounds.Intersects(enemy.WorldBounds) == true)
                        {
                            delete_enemy.Push(enemy);
                            delete_list.Push(missile);
                            break;
                        }
                    }
                }

                while (delete_list.Count > 0 || delete_enemy.Count > 0)
                {
                    if (delete_list.Count > 0)
                    {
                        Missile temp = delete_list.Pop();
                        m_Player.m_Ship.missiles.Remove(temp);
                        ScreenManager.Game.Components.Remove(temp);
                    }
                    if (delete_enemy.Count > 0)
                        enemyManager.remove_enemy(delete_enemy.Pop());
                }

                // Ship-Planet Collision
                if (planet.WorldBounds.Intersects(m_Player.m_Ship.WorldBounds))
                {
                    // Calculate the normal of the planet at point of collision.
                    Vector3 normal = m_Player.m_Ship.GetWorldPosition() - planet.GetWorldPosition();
                    normal.Normalize();

                    // Determine how much damage should be done.
                    float collisionValue = (Vector3.Dot(m_Player.m_Ship.m_vVelocity, normal) * normal).Length();
                    m_Player.TakeDamage((int)collisionValue / 50);

                    // Calculate reflection vector.
                    Vector3 reflectionVec = Vector3.Reflect(m_Player.m_Ship.m_vVelocity, normal);

                    // Factor in how bouncy the planet is.
                    //reflectionVec *= Planet.Bounciness;

                    // Prevents the ship from sticking to the planet.
                    while (Vector3.Dot(reflectionVec, normal) < 0)
                        reflectionVec = Vector3.Reflect(reflectionVec, normal);

                    m_Player.m_Ship.m_vVelocity = reflectionVec;
                }

                // End game conditions.

                // Check if ship is destroyed
                if (m_Player.IsShipDestroyed())
                    EndGame();

                // Check if the star sphere is done contracting.
                if (ShrinkingStarSphere.GetShrinkingStarSphere().IsFinishedContracting())
                    EndGame();

                // Increase player score.
                currentScore += fDelta;
            }
        }


        /// <summary>
        /// Lets the game respond to player input. Unlike the Update method,
        /// this will only be called when the gameplay screen is active.
        /// </summary>
        public override void HandleInput(InputState input, GameTime gameTime)
        {
            if (!gameOver)
            {
                if (input == null)
                    throw new ArgumentNullException("input");

                fDelta = (float)gameTime.ElapsedGameTime.TotalSeconds;

                if (input.PauseGame)
                {
                    // If they pressed pause, bring up the pause menu screen.
                    ScreenManager.AddScreen(new PauseMenuScreen());
                    pause = !pause;
                    return;
                }

                if (input.play_sound)
                {
                    soundOverInstance.Play();
                }

                //create a circle around the center of the screen that the mouse should occupy. modify the below code such that the mouse never goes beyond the radius

                mouse_vector.Y = input.Y_coordinate - center_y;
                mouse_vector.X = input.X_coordinate - center_x;

                if (mouse_vector.Length() > max_distance_from_center)
                {
                    mouse_vector.Normalize();
                    mouse_vector *= max_distance_from_center;

                    input.set_mouse_position((int)mouse_vector.X + center_x, (int)mouse_vector.Y + center_y);
                }


                if (!respawning)
                {
                    m_Player.m_Ship.shipDecelerationForce.vec = Vector3.Zero;
                    if (!m_Player.m_Ship.withinWormholeRadius)
                    {
                        if (input.ShipMoveForward && !input.ShipMoveBackward)
                        {
                            m_Player.m_Ship.shipControlForce = Ship.ForwardThrust * m_Player.m_Ship.WorldTransform.Forward;
                        }
                        else if (input.ShipMoveBackward && !input.ShipMoveForward)
                        {
                            m_Player.m_Ship.shipControlForce = Ship.BackwardThrust * m_Player.m_Ship.WorldTransform.Backward;
                        }
                        else if (!input.ShipMoveBackward && !input.ShipMoveForward)
                        {
                            m_Player.m_Ship.shipControlForce = Vector3.Zero;
                            m_Player.m_Ship.shipDecelerationForce.vec = -m_Player.m_Ship.m_vVelocity * Ship.DecelerationConst;
                        }

                        /*
                            *How to do left/right rotation
                            *if 'e' key pressed,rotate left, if 'f' key pressed, rotate right
                            */

                        if (input.ShipRotateLeft)
                        {
                            m_Player.m_Ship.m_qRotation *= Quaternion.CreateFromAxisAngle(Vector3.Forward, fDelta * 1f);
                        }

                        if (input.ShipRotateRight)
                        {
                            m_Player.m_Ship.m_qRotation *= Quaternion.CreateFromAxisAngle(Vector3.Forward, -fDelta * 1f);
                        }
                    }
                    else
                    {
                        m_Player.m_Ship.shipControlForce = Vector3.Zero;
                    }

                    if (!using_mouse)
                    {
                        if (input.ShipTurnLeft && !input.ShipTurnRight)
                        {
                            m_Player.m_Ship.m_qRotation *= Quaternion.CreateFromAxisAngle(Vector3.Up, fDelta * MathHelper.ToRadians(Ship.ShipRotSpeed));
                        }

                        if (input.ShipTurnRight && !input.ShipTurnLeft)
                        {
                            m_Player.m_Ship.m_qRotation *= Quaternion.CreateFromAxisAngle(Vector3.Up, fDelta * -MathHelper.ToRadians(Ship.ShipRotSpeed));
                        }
                    }
                    else if (using_mouse && mouse_vector.Length() > 25)
                    {
                        //then do left and right seperately. 
                        //first try to get up and down rotation
                        //then combine (hopefully works)
                        //Vector3 perp_vector;
                        Vector2 temp_mouse = mouse_vector;
                        temp_mouse.Normalize();
                        double angle = Math.Acos(Vector2.Dot(Vector2.UnitX, temp_mouse));
                        m_Player.m_Ship.m_qRotation *= Quaternion.CreateFromAxisAngle(Vector3.Up, -mouse_vector.X * fDelta / max_distance_from_center);
                        m_Player.m_Ship.m_qRotation *= Quaternion.CreateFromAxisAngle(Vector3.Right, -mouse_vector.Y * fDelta / max_distance_from_center);
                    }

                    if (input.ShipFire)
                    {
                        m_Player.m_Ship.FireMissile();
                    }
                }
            }
        }

        /// <summary>
        /// Draws the gameplay screen.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            ScreenManager.GraphicsDevice.Clear(ClearOptions.Target,
                                                Color.Black, 0, 0);

            // Our player and enemy are both actually just text strings.
            SpriteBatch spriteBatch = ScreenManager.SpriteBatch;

            spriteBatch.Begin();

            // Skybox drawing
            ScreenManager.GraphicsDevice.RasterizerState = RasterizerState.CullClockwise;
            sky_box.Draw(FollowCamera.m_CameraMatrix, FollowCamera.m_ProjectionMatrix, FollowCamera.vActualCameraPosition);
            ScreenManager.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;

            spriteBatch.End();

            // If the game is transitioning on or off, fade it out to black.
            if (TransitionPosition > 0)
                ScreenManager.FadeBackBufferToBlack(255 - TransitionAlpha);
        }

        /// <summary>
        /// Load the game over screen and do other end game stuff.
        /// </summary>
        public void EndGame()
        {
            // Set gameOver flag to tell everyone to stop updating.
            gameOver = true;

            // Stop game music.
            MediaPlayer.Stop();

            // Play the game end sound.
            soundOverInstance.Play();

            // Open the game over screen.
            ScreenManager.AddScreen(new GameOverScreen());
        }

        #endregion
    }
}
